package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.aspectj.weaver.AjAttribute;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ：MJS
 * @date ：Created in 2021/5/15  15:52
 * @description：TODO
 * 搜索
 * 添加
 * 修改
 * 删除
 *
 */
@Service
public class ListServiceImpl implements ListService {
    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    //Spring官方封装   将下面的封装了 //ES 官方近期高级别的客户端
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;//搜索方面性能极佳
    // ES 官方近期高级别的客户端
    @Autowired
    private RestHighLevelClient restHighLevelClient;//搜索方面性能极佳  效果更好一点
    @Autowired
    private RedisTemplate redisTemplate;
    //上架之导入数据
    @Override
    public void onSale(Long skuId) {
        Goods goods=new Goods();
        //1.SkuInfo   库存表
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        //实时价格
        BigDecimal price = productFeignClient.getPrice(skuId);
        goods.setPrice(price.doubleValue());
        //时间
        Calendar instance = Calendar.getInstance();
//        instance.setTimeZone();
        goods.setCreateTime(instance.getTime());
        //2.品牌表
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());
        //3.三级分类视图
        BaseCategoryView baseCategoryView = productFeignClient.getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());
        //4.平台属性表    入参：skuId  库存对应的凭条属性名称ID及值
        List<SkuAttrValue> skuAttrValueList = productFeignClient.getSkuAttrValue(skuId);
        //偷天换日
        //结果
        List<SearchAttr> attrList=skuAttrValueList.stream().map(skuAttrValue->{
            SearchAttr searchAttr=new SearchAttr();
            //平台属性ID
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            //平台属性名称
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            //平台属性值名称
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
                }).collect(Collectors.toList());
        goods.setAttrs(attrList);
        goodsDao.save(goods);
    }
    //下架之导入数据
    @Override
    public void cancelSale(Long skuId) {
        //删除
        goodsDao.deleteById(skuId);
    }
    //增加热度                     增加评论数 增加晒单数
    @Override
    public void hotScore(Long skuId) {
        /*
        * 方案：攒一堆再修改ES索引库
        * Redis
        *   五大数据类型
        *    string类型（字符串）
        * hash类型 （散列类型）
        * list类型 (列表类型)
        * set类型（集合类型）
        * zset类型（有序集合类型）
        *
        * */
        //1.热度分  先保存缓存  分数达到一定的阈值  再保存到ES索引库
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, 1);

        if(hotScore%10==0){
            //1.查询
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            goods.setHotScore(Math.round(hotScore));//四舍五入
            //2.保存
            goodsDao.save(goods);//判断id，相同是修改  此保存的底层，是先删除再添加    id不同是添加
         }

    }
    //开始搜索   比较难
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1、构建条件请求对象
        SearchRequest searchRequest=buildSearchRequest(searchParam);
        //2、执行搜索
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//意思是使用默认Get
            //3、解析结果
            SearchResponseVo vo=parseSearchResponse(searchResponse);
            //当前页
            vo.setPageNo(searchParam.getPageNo());
            //每页数
            vo.setPageSize(searchParam.getPageSize());
            //总页数  =（总条数+每页数-1）/每页数
            long totalPages=(vo.getTotal()+vo.getPageSize()-1)/vo.getPageSize();
            vo.setTotalPages(totalPages);
            return vo;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
    //解析搜索结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo=new SearchResponseVo();

        SearchHits hits = searchResponse.getHits();
        //1.总条数
        long totalHits = hits.getTotalHits();
        System.out.println("总条数："+totalHits);
        vo.setTotal(totalHits);
        //2.商品结果集
        SearchHit[] hits1 = hits.getHits();
        //商品结果集
        List<Goods>goodsList= Arrays.stream(hits1).map(searchHit->{

            String sourceAsString = searchHit.getSourceAsString();
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            System.out.println("无高亮的名称"+goods.getTitle());
            //解析出高亮的名称  将无高亮的名称替换掉
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if(null!=highlightFields&& highlightFields.size()>0){//为了防止空指针异常
                HighlightField title = highlightFields.get("title");
                goods.setTitle(title.fragments()[0].toString());
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //3.品牌集合
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg =(ParsedLongTerms) aggregationMap.get("tmIdAgg");//因为品牌ID为long，所以用ParsedLongTerms比较好
       //偷天换日
        List<SearchResponseTmVo>trademarkList=tmIdAgg.getBuckets().stream().map(bucket->{
           SearchResponseTmVo tmVo = new SearchResponseTmVo();
           //品牌Id
           tmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));
           Aggregations aggregations = ((Terms.Bucket) bucket).getAggregations();
           //品牌名称
           ParsedStringTerms tmNameAgg = aggregations.get("tmNameAgg");
           tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
           //品牌Logo
           ParsedStringTerms tmLogoUrlAgg = aggregations.get("tmLogoUrlAgg");
           tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
       }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);
        //4.平台集合
        ParsedNested attrsAgg = (ParsedNested)aggregationMap.get("attrsAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket->{
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性id
            attrVo.setAttrId(Long.parseLong(((Terms.Bucket) bucket).getKeyAsString()));
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //平台属性值列表

            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            //偷天换日
            attrVo.setAttrValueList(attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()));
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(attrsList);
        return vo;
    }

    //构建条件对象
    public SearchRequest buildSearchRequest(SearchParam searchParam){
        //1.构建搜索请求对象   GET goods/_search
        SearchRequest searchRequest=new SearchRequest();
        //2.构建搜索资源条件对象
        /*
         * DSL语句
         * {
         *    query:{
         *      match_all:{}
         *    }
         * }
         * */
        SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
        //组合条件对象
        BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
        //1.关键词
        String keyword= searchParam.getKeyword();
        //需要先判断一下
        if(!StringUtils.isEmpty(keyword)){
            //匹配指定的域及值
//            searchSourceBuilder.query(QueryBuilders.matchQuery("title",keyword));
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
        }else{
            //匹配全部    ES官方工具类    QueryBuilders代表工具类
//            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //2.过滤条件
          //2.1 品牌  品牌ID：品牌名称
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)){
            String[] t = StringUtils.split(trademark, ":");
            //0:品牌ID  1：品牌名称
//            searchSourceBuilder.query(QueryBuilders.termQuery("tmId",t[0]));
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",t[0]));
        }
          //2.2 一二三级分类

        Long category1Id = searchParam.getCategory1Id();
        if(null!=category1Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if(null!=category2Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if(null!=category3Id){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }
        //2.3 平台属性   比较难
        String[] props = searchParam.getProps();
        if(null!=props && props.length>0){
            //第二个boolean
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            for (String prop : props) {
                //props  =23:4G:运行内存   平台属性ID：平台属性值：平台属性名称
                String[] p = prop.split(":");
                if(null!=p && p.length==3){
                    //第三个boolean
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",p[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",p[1]));
                    //将每次的第三个boolean筛选出来的东西放在第二个Boolean里
                    boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
                }
            }
            //第一个boolean
            boolQueryBuilder.filter(boolQuery);
        }
        //设置组合条件对象
        searchSourceBuilder.query(boolQueryBuilder);
        //3.排序
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            //order： 1：asc  1：综合  2：价格
            String[] o = order.split(":");
            String name=null;
            switch (o[0]){
                case "1" :
                    name="hotScore";
                    break;
                case "2" :
                    name="price";
                    break;
            }
            searchSourceBuilder.sort(name,o[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC);
        }else{
            //没有排序字段  默认按照热度  由高到低 降序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //4.分页
        Integer pageNo = searchParam.getPageNo();//当前页
        Integer pageSize = searchParam.getPageSize();//每页数
        searchSourceBuilder.from((pageNo-1)*pageSize);//limit  开始行
        searchSourceBuilder.size(pageSize); //limit  开始行的每页数
        //5.聚合  品牌  页面显示  品牌名称 品牌logo 品牌ID
        //使用复杂聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")//terms（）给自己的聚合起名字 field是属性本身的名字
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))//terms（）给自己的聚合起名字
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));//terms（）给自己的聚合起名字
        //6.聚合平台属性, 平台属性入口1--平台属性ID2---其他作为3
        //聚合在此相当于分组
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                 .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));
        //7.高亮
        HighlightBuilder highlightBuilder=new HighlightBuilder();
        highlightBuilder
                .field("title")
                .preTags("<font color ='red'>")
                .postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        HighlightBuilder highlighter = searchSourceBuilder.highlighter();
        highlighter.preTags("<span style='color:green'>")
                .postTags("</span>");


        System.out.println("DSL"+searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        //设置索引库
        searchRequest.indices("goods");
/*        //设置表
        searchRequest.types("info");*/
        return searchRequest;
    }

}
